package cn.edu.zzuli.nothinglink.service.impl;

import cn.edu.zzuli.nothinglink.entity.*;
import cn.edu.zzuli.nothinglink.mapper.*;
import cn.edu.zzuli.nothinglink.service.CourseService;
import cn.edu.zzuli.nothinglink.service.WorkService;
import cn.edu.zzuli.nothinglink.to.StuSubmitInfoTo;
import cn.edu.zzuli.nothinglink.vo.CourseInfoVo;
import cn.edu.zzuli.nothinglink.vo.CourseTeacherVo;
import cn.edu.zzuli.nothinglink.vo.WorkConditionVo;
import cn.edu.zzuli.nothinglink.ws.CourseNewEndPoint;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author suzyyy
 * @since 2021-04-08
 */
@Service
public class CourseServiceImpl extends ServiceImpl<CourseMapper, Course> implements CourseService {

    @Resource
    CourseMapper courseMapper;

    @Resource
    StudentsMapper studentsMapper;

    @Resource
    LiveInfoMapper liveInfoMapper;

    @Resource
    TeachersMapper teachersMapper;

    @Autowired
    ThreadPoolExecutor executor;

    @Resource
    WorkMapper workMapper;

    @Resource
    WorkService workService;

    @Resource
    WorkSubmitRecordMapper workSubmitRecordMapper;


    @Override
    public R createCourse(CourseInfoVo vo, Integer uId) {

        //JSR303 校验后，我们的参数的问题已经可以排除了，这里可以直接写业务
        Course c = new Course();
        BeanUtils.copyProperties(vo, c);
        String courseURL = UUID.randomUUID().toString().replaceAll("-", "");
        c.setCourseTeaId(uId).setCourseUrl(courseURL);

        //遍历 vo中班级id
        ArrayList<Integer> classIds = (ArrayList<Integer>) vo.getClassIds();
        CourseNewEndPoint.send2UserById(uId,
                R.success().add("msg", "开始创建课程...")
        );

        //搜索对应id的学生，进行学生和课程双向绑定。
        List<Students> students = studentsMapper.selectList(
                new LambdaQueryWrapper<Students>().select(
                        Students::getStuId, Students::getStuName
                ).in(Students::getBelongClassId, classIds)
        );
        //设置班级人数
        c.setCourseNumber(students.size());

        //添加一条新的课程记录
        //添加失败回滚
        if (!save(c)) {
            throw new RuntimeException();
        }

        CourseNewEndPoint.send2UserById(uId,
                R.success().add("msg", "开始指定课程和班级信息的双向绑定...")
        );

        //进行，course-class，双向绑定
        boolean b = courseMapper.bindCourseAndClass(c.getCourseId(), classIds);
        if (!b) throw new RuntimeException();

        CourseNewEndPoint.send2UserById(uId,
                R.success().add("msg", "开始进行特定班级的学生和课程的绑定...若需要，请等待稍候返回的url")
        );

        boolean b2 = courseMapper.bindCourseAndStu(c.getCourseId(), c.getCourseName(), students);
        if (!b2) throw new RuntimeException();

        Course byId = getById(c.getCourseId());

        return R.success().add("course", byId);
    }

    @Override
    public R getMyCoursesList(Integer uId) {
        //默认获取 createTime < now < endTime ，其余排除
        //按照创建时间降序排列
        List<Course> courses = this.list(
                new LambdaQueryWrapper<Course>()
                        .eq(Course::getCourseTeaId, uId)
                        .ge(Course::getEndTime, LocalDateTime.now())
                        .le(Course::getCreateTime, LocalDateTime.now())
                        .orderByDesc(Course::getCreateTime)
        );

        return R.success().add("courses", courses);
    }

    @Override
    public CourseInfoVo getCourseInfo(Integer courseId) {
        CourseInfoVo vo = new CourseInfoVo();
        List<Classes> classesInCourse = courseMapper.getClassInTheCourses(courseId);
        Course c = getById(courseId);
        List<Integer> classIds = new ArrayList<>();
        List<String> bindClassNames = new ArrayList<>();
//                classesInCourse.stream()
//                        .map(cl -> cl.getClassId()).collect(Collectors.toList());
        classesInCourse.forEach(classInfo -> {
            bindClassNames.add(classInfo.getClassRealName());
            classIds.add(classInfo.getClassId());
        });
        BeanUtils.copyProperties(c, vo);
        vo.setClassIds(classIds);
        vo.setBindClassNames(bindClassNames);

        return vo;
    }

    @Transactional
    @Override
    public R updateTheCourseInfo(CourseInfoVo vo) {
        Course c = new Course();
        BeanUtils.copyProperties(vo, c);
        Course db_c = getById(c.getCourseId());
        c.setCourseUrl(null);

        //基础信息直接调用方法修改就行了
        updateById(c);

        CompletableFuture<Void> updateNameTask = CompletableFuture.runAsync(() -> {
            if (!db_c.getCourseName().equals(c.getCourseName())) {
                //修改student_course表，存储的中间course_name
                courseMapper.updateTheCourseNameInStuCourse(c.getCourseName(), c.getCourseId());
            }

        }, executor);

        CompletableFuture<Void> updateClassIdsTask = CompletableFuture.runAsync(() -> {
            // spring无法处理thread的事务，声明式事务无效，
            // 目前的情况是捕捉异步子线程后，throw new RuntimeException() 可以使主线程回滚
            // 但是现在，主线程异常后，子线程却不会回滚
            // 严格要求的话，应该无论哪个线程报错，所有线程跟着回滚。
            // 我现在能想到的是解决方法：
            // 为每个线程创建一个事务，最后在主线程出错的时候，手动控制所有事务跟着回滚

            // 这里就自己手动控制了，因为一般执行这条sql是不会报错的。


            //但是修改班级-课程。二者绑定关系的时候就比较复杂了。
            //首先要判断传过来的classIds 数量是否发送了变化
            List<Integer> classIds = vo.getClassIds();
            List<Integer> db_classIds = courseMapper.getTheClassesInCourse(c.getCourseId())
                    .stream().map(cl -> cl.getClassId())
                    .collect(Collectors.toList());

            //两个 size 相等的时候，说明无需变化 课程-班级绑定信息。
            if (db_classIds.size() == classIds.size()) return;

            //将 List 转为哈希表 防止循环次数变为 m*n
            Map<Integer, Integer> classIds_map =
                    classIds.stream()
                            .collect(Collectors.toMap(integer -> integer, integer -> integer));

            Map<Integer, Integer> db_classIds_map =
                    db_classIds.stream()
                            .collect(Collectors.toMap(integer -> integer, integer -> integer));

            if (db_classIds.size() > classIds.size()) {
                System.out.println("数量减少了");
                //此时classIds 确实发生了变化，而且是减少了。
                //所以我们要过滤出，这两个list中，有哪些是不一样的，
                //这个不一样的就是 我们要删除的班级ID
                Set<Integer> ne_classIds = fromSizeLessStartForEach(classIds_map, db_classIds_map);
                //批量删除，其实也就是修改 deleted 为 1
                courseMapper.delTheBindCourseAndStu(c.getCourseId(), ne_classIds);

            } else if (db_classIds.size() < classIds.size()) {
                System.out.println("数量变多了");
                //这个时候，说明相比之前，反而添加了一些班级
                //这个时候，我们仍然要遍历出不同的数据
                //此时不同的数据意味着，我们要添加的班级ID
                Set<Integer> ne_classIds = fromSizeLessStartForEach(db_classIds_map, classIds_map);
                courseMapper.bindCourseAndClass(c.getCourseId(), ne_classIds);

            }

        }, executor);

        //修改work中的作业名字
        workService.update(new LambdaUpdateWrapper<Work>()
                .set(Work::getCourseName, c.getCourseName())
                .eq(Work::getCourseId, c.getCourseId()) );

        try {
            CompletableFuture.allOf(updateNameTask, updateClassIdsTask).get();
            return R.success();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
            throw new RuntimeException();
        }

    }

    @Override
    public R getMyCoursesInMyClass(Integer stuId) {
        Students students = studentsMapper.selectById(stuId);

        Integer classId = students.getBelongClassId();
        if (classId == 0) return R.error().add("error", "请先绑定班级").add("courses", null);

        //获取classId绑定的课程
        List<Course> myCourses = courseMapper.getMyCoursesInTheClass(classId, LocalDateTime.now());

        ConcurrentHashMap<Integer, CourseTeacherVo> teachersVoMap = new ConcurrentHashMap<>();

        //使用parallelStream 并发去读的话，由于没测试，所以我不知道和这个代码相比，哪个性能更好点。
        //但并发去读的时候，也可能会建立更多的连接。
        //比如现在数据库有4条同意老师的数据，那么极大的可能是一次性去读这4条老师的数据和直播间数据。
        //所以这里先直接forEach去读
        myCourses.forEach(c -> {

            //此if判断的目的是尽可能减少连接mysql去读取同一个教师数据。
            if (teachersVoMap.containsKey(c.getCourseTeaId())) {
                c.setTeacherInfo(teachersVoMap.get(c.getCourseTeaId()));
            }else {
                //获取老师的具体信息
                Teachers t = teachersMapper.selectById(c.getCourseTeaId());
                LiveInfo liveInfo = liveInfoMapper.selectOne(new LambdaQueryWrapper<LiveInfo>().eq(
                        LiveInfo::getLiveBelongTeaId, t.getTeaId()
                ));
                CourseTeacherVo vo = new CourseTeacherVo(t.getTeaName(),t.getTeaId(), liveInfo.getLivePullUrl());
                teachersVoMap.put(t.getTeaId(), vo);
                c.setTeacherInfo(vo);
            }
        });

        return R.success().add("courses", myCourses);
    }

    @Override
    public R getStusInCourse(Integer courseId) {
        //首先要获取绑定全部班级
        Map<Integer, Classes> class_map = courseMapper.getClassInTheCourses(courseId).stream()
                .collect(Collectors.toMap(k -> k.getClassId(), v -> v));

        //获取班级的全部学生
        List<Students> students = studentsMapper.selectList(
                new LambdaQueryWrapper<Students>().in(Students::getBelongClassId, class_map.keySet())
        );

        //获取该课程全部的作业
        Map<Integer, Work> work_map = workMapper.selectList(
                new LambdaQueryWrapper<Work>()
                        .eq(Work::getCourseId, courseId)
                        .orderByAsc(Work::getEnd)//按照end升序，即0->1,未结束的作业在前。
                        .orderByAsc(Work::getEndTime)//按照时间升序，即时间结束日期较近的作业在前
        ).stream().collect(Collectors.toMap(k -> k.getWorkId(), v -> v));



        students.parallelStream().forEach(s -> {
            //设置班级名字
            s.setClassName(class_map.get(s.getBelongClassId()).getClassName());

            //获取该学生的提交次数和分数。
            if (work_map.size() != 0) {
                StuSubmitInfoTo stusSubmitInfo = workSubmitRecordMapper.getStusSubmitInfo(s.getStuId(), work_map.keySet());
                s.setSubmitInfo(stusSubmitInfo);
            }else {
                s.setSubmitInfo(new StuSubmitInfoTo(0, 0.0));
            }
        });

        //获取该学生
        return R.success().add("stus", students).add("workCount", work_map.size());
    }

    @Override
    public R getWorkConditionByCourseId(Integer courseId) {

        //获取该课程全部的作业
        LocalDateTime now = LocalDateTime.now();
        List<Work> works = workMapper.selectList(
                new LambdaQueryWrapper<Work>()
                        .eq(Work::getCourseId, courseId)
                        .orderByAsc(Work::getStartTime)
        );
        int workCount = works.size();

        if (workCount == 0) {
            return R.success()
                    .add("workCount", workCount)
                    .add("workCondition", null)
                    .add("submissionRate", 1.0)
                    .add("scoringRate", 1.0);
        }

        //计算出全部的作业分数。
        int totalScore = works.stream().mapToInt(Work::getWorkScore).sum();

        System.out.println("该课程的作业所累加的总分数：" + totalScore * 1.0);

        //获取全部的作业id
        List<Integer> workIds = works.stream().map(Work::getWorkId).collect(Collectors.toList());

        //获取课程绑定的全部课程
        Map<Integer, Classes> classes_map = courseMapper.getTheClassesInCourse(courseId)
                .stream()
                .collect(Collectors.toMap(k -> k.getClassId(), v -> v));

        //获取班级全部的学生
        List<Students> stus = studentsMapper.getStusWhenClassInList(classes_map.keySet());

        int stuCount = stus.size();
        System.out.println("该课程绑定的全部学生数量" + stuCount);

        int needTotalSubmitCount = stuCount * workCount;
        int needTotalScore = totalScore * stuCount;
        System.out.println("该课程所需要的全部提交数：" + needTotalSubmitCount);
        System.out.println("该课程所需要的作业全部分数：" + needTotalScore);

        AtomicInteger realSubmitCount = new AtomicInteger(0);
        AtomicReference<Double> realScore = new AtomicReference<>(0.0);
        CopyOnWriteArrayList<WorkConditionVo> conditionVos = new CopyOnWriteArrayList<>();

        //获取每个作业的提交数量，得分率
        works.parallelStream().forEach(w -> {
            //获取该作业全部的作业提交记录
            List<WorkSubmitRecord> records = workSubmitRecordMapper.selectList(
                    new LambdaQueryWrapper<WorkSubmitRecord>().eq(WorkSubmitRecord::getWorkId, w.getWorkId())
            );

            double score = records.stream().mapToDouble(WorkSubmitRecord::getScore).sum();
            realScore.updateAndGet(v -> new Double((double) (v + score)));

            realSubmitCount.addAndGet(records.size());

            //计算得分率;
            double scoringRate = score / (stuCount * w.getWorkScore());

            conditionVos.add(
                    new WorkConditionVo().setWorkName(w.getWorkName())
                            .setScoringRate(scoringRate)
                            .setSubmitCount(records.size())
            );

        });

        return R.success()
                .add("workCount", workCount)
                .add("workCondition", conditionVos)
                .add("submissionRate", (realSubmitCount.get() * 1.0) / needTotalSubmitCount)
                .add("scoringRate", realScore.get() / needTotalScore);
    }

    /**
     * 既然我们要过滤数据，那么没办法，我们只能从size 较少的一个开始，
     * 来遍历出我们需要的
     * <不同>数据
     * @param less 数据较少的map
     * @param more 数据较多的map
     * @return
     */
    private Set<Integer> fromSizeLessStartForEach(Map<Integer, Integer> less,
                                                  Map<Integer, Integer> more) {
        less.forEach((k, v) -> {
            if (more.containsKey(k))
                more.remove(k);
        });

        return more.keySet();
    }
}
